﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using TeachMe.Application.Configuration;
using TeachMe.Application.Data;
using TeachMe.Application.Events;
using TeachMe.Application.Exceptions;
using TeachMe.Application.Properties;
using TeachMe.Application.Win32;

namespace TeachMe.Application.Forms
{
    using TeachMe.Application.ComboItems;
    using System.Diagnostics;

    public partial class EntriesEditorForm : PseudoModalForm
    {
        private class CurrentDictionaryWrapper
        {
            private Dictionary _dictionary;

            public Dictionary Dictionary
            {
                get { return _dictionary; }
                set
                {
                    if (_dictionary != null)
                    {
                        _dictionary.EntriesModified -= OnDictionaryChanged;
                    }
                    _dictionary = value;
                    if (_dictionary != null)
                    {
                        _dictionary.EntriesModified += OnDictionaryChanged;
                    }
                }
            }

            public event EventHandler<EventArgs> Changed;

            private void OnDictionaryChanged(object sender, DictionaryEntriesEventArgs e)
            {
                EventHandler<EventArgs> handler = Changed;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }

        private enum FindColumnType
        {
            All,
            From,
            To,
            Example
        }

        private delegate EditedEntryDescriptor PerformDelegate();

        private readonly CurrentDictionaryWrapper _currentDictionary;

        /// <summary>
        /// We cannot use dicts from root because there may be some deleted dicts (when opened from settings dlg)
        /// which would prevent us from using same file name for dict.
        /// </summary>
        private readonly List<Dictionary> _dicts;

        private readonly Root _root;
        private List<EditedEntryDescriptor> _deletedEntries;
        private List<EditedEntryDescriptor> _entries;
        private bool _savingChanges;
        private int _skipChangedDictionaryCounter;

        public EntriesEditorForm(Root root, List<Dictionary> dicts, Dictionary defaultSelection = null)
        {
            _root = root;
            _root.MachineSettings.CheckOut();

            _entries = new List<EditedEntryDescriptor>();
            _deletedEntries = new List<EditedEntryDescriptor>();

            _dicts = dicts;

            _currentDictionary = new CurrentDictionaryWrapper();
            _currentDictionary.Dictionary = defaultSelection;
            _currentDictionary.Changed += CurrentDictionaryChanged;

            InitializeComponent();
            InitializeGrid();
            InitializePriorityChanger();
            InitializeFindColumns();
            InitializeEditDirectlyButton();

            cbFrom.Fill();
            cbTo.Fill();

            entriesBindingSource.DataSource = _entries;

            FillDictionaries();

            if (_currentDictionary != null && _currentDictionary.Dictionary != null)
            {
                FillEntries();
                FillMetadata();

                cbDictionaries.SelectedItem = defaultSelection;
            }

            UpdateButtons();

            WindowProportions.Lock(this, _root.MachineSettings);
            GridProportions.Lock(this, grid, _root.MachineSettings);

            if (_currentDictionary == null || _currentDictionary.Dictionary == null)
            {
                tcMain.SelectedTab = tpMetadata;
            }
        }

        /// <summary>
        /// Dict created in current session.
        /// </summary>
        public Dictionary CreatedDictionary { get; private set; }

        private int LastSelectedRowIndex
        {
            get { return GetSelectedRows().Max(x => x.Index); }
        }

        private int FirstSelectedRowIndex
        {
            get { return GetSelectedRows().Min(x => x.Index); }
        }

        private void InitializeFindColumns()
        {
            string[] colsNames = Resources.DictionaryFindColumns.Split('\t');
            tscbFindWhere.Items.Add(new ComboBoxItem<FindColumnType>(FindColumnType.All, colsNames[0]));
            tscbFindWhere.Items.Add(new ComboBoxItem<FindColumnType>(FindColumnType.From, colsNames[1]));
            tscbFindWhere.Items.Add(new ComboBoxItem<FindColumnType>(FindColumnType.To, colsNames[2]));
            tscbFindWhere.Items.Add(new ComboBoxItem<FindColumnType>(FindColumnType.Example, colsNames[3]));

            tscbFindWhere.SelectedItem = tscbFindWhere.Items[0];
        }

        private void InitializePriorityChanger()
        {
            miPriority.Text = Resources.ColumnPriority;

            miPriority.DropDownItems.Add(Resources.VeryLowPriority, Resources.priority_very_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.VeryLow));
            miPriority.DropDownItems.Add(Resources.LowPriority, Resources.priority_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Low));
            miPriority.DropDownItems.Add(Resources.NormalPriority, Resources.priority_normal).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Normal));
            miPriority.DropDownItems.Add(Resources.HighPriority, Resources.priority_high).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.High));

            tsbPriority.DropDownItems.Add(Resources.VeryLowPriority, Resources.priority_very_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.VeryLow));
            tsbPriority.DropDownItems.Add(Resources.LowPriority, Resources.priority_low).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Low));
            tsbPriority.DropDownItems.Add(Resources.NormalPriority, Resources.priority_normal).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.Normal));
            tsbPriority.DropDownItems.Add(Resources.HighPriority, Resources.priority_high).Click += ((sender, e) => ChangePriorityForSelectedItems(Priority.High));
        }

        private IEnumerable<EditedEntryDescriptor> GetSortedSelection()
        {
            return GetSelectedRows().OrderBy(x => x.Index).Select(x => _entries[x.Index]);
        }

        private IEnumerable<EditedEntryDescriptor> GetSelection()
        {
            return GetSelectedRows().Select(x => _entries[x.Index]);
        }

        private int GetSelectionSize()
        {
            return GetSelectedRows().Count();
        }

        private bool IsNewRowSelected()
        {
            return grid.SelectedCells.Cast<DataGridViewCell>().Select(x => grid.Rows[x.RowIndex]).Any(x => x.IsNewRow);
        }

        private IEnumerable<DataGridViewRow> GetSelectedRows()
        {
            return grid.SelectedCells.Cast<DataGridViewCell>().Select(x => grid.Rows[x.RowIndex]).Where(x => !x.IsNewRow).Distinct();
        }

        private void RefreshGrid()
        {
            grid.Refresh();
        }

        private bool IsEditing()
        {
            return grid.IsCurrentCellInEditMode;
        }

        private void Select(EditedEntryDescriptor descriptor, int column = 0)
        {
            if (descriptor != null)
            {
                int row = _entries.IndexOf(descriptor);

                grid.CurrentCell = null;
                grid.CurrentCell = grid.Rows[row].Cells[column];
            }
        }

        private void SelectAll()
        {
            grid.SelectAll();
        }

        private void CancelEditing()
        {
            grid.CancelEdit();
        }

        private void ChangePriorityForSelectedItems(Priority priority)
        {
            foreach (EditedEntryDescriptor descriptor in GetSelection())
            {
                descriptor.Priority = priority;
            }

            RefreshGrid();
            UpdateButtons();
        }

        private void CurrentDictionaryChanged(object sender, EventArgs e)
        {
            if (_savingChanges)
            {
                return;
            }

            if (MessageBox.Show(Resources.DictionaryChangedYouWantReload, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                Revert();
            }
        }

        private void InitializeGrid()
        {
            // fixing drawing performance issues ??
            typeof(DataGridView).InvokeMember("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, grid, new object[] { true });

            priorityBindingSource.DataSource = ComboBoxItemsStore.Priorities;

            entriesBindingSource.AllowNew = true;
            entriesBindingSource.AddingNew += OnAddingNew;

            grid.CellParsing += OnGridCellParsing;
        }

        private void OnAddingNew(object sender, System.ComponentModel.AddingNewEventArgs e)
        {
            e.NewObject = EditedEntryDescriptor.CreateNew();
        }

        private void OnGridCellParsing(object sender, DataGridViewCellParsingEventArgs e)
        {
            if (e.ColumnIndex == colPriority.Index)
            {
                e.Value = ComboBoxItemsStore.Priorities.First(x => x.ToString().Equals(e.Value));
                e.ParsingApplied = true;
            }
        }

        private void FillMetadata()
        {
            tbFileName.ReadOnly = true;
            bBrowseForFileName.Enabled = false;

            tbFileName.Text = _currentDictionary.Dictionary.UserData.Path;
            tbFileName.Modified = false;
            tbName.Text = _currentDictionary.Dictionary.Entries.Name;
            tbName.Modified = false;
            cbFrom.Code = _currentDictionary.Dictionary.Entries.From;
            cbFrom.Modified = false;
            cbTo.Code = _currentDictionary.Dictionary.Entries.To;
            cbTo.Modified = false;
            tbDescription.Text = _currentDictionary.Dictionary.Entries.Description;
            tbDescription.Modified = false;
        }

        private void FillEntries()
        {
            entriesBindingSource.Clear();

            _deletedEntries.Clear();

            AlterEntries(delegate
                             {
                                 foreach (Entry entry in _currentDictionary.Dictionary.Entries.Items.Values)
                                 {
                                     entriesBindingSource.Add(EditedEntryDescriptor.Create(entry, _currentDictionary.Dictionary.GetEntryPriority(entry.Id)));
                                 }

                                 return null;
                             });
        }

        private void FillDictionaries()
        {
            object currentSelection = cbDictionaries.SelectedItem ?? CreatedDictionary;

            cbDictionaries.Items.Clear();

            foreach (Dictionary dictionary in _dicts)
            {
                cbDictionaries.Items.Add(dictionary);
            }

            try
            {
                _skipChangedDictionaryCounter++;

                cbDictionaries.SelectedItem = currentSelection;
            }
            finally
            {
                _skipChangedDictionaryCounter--;
            }
        }

        private void OnAddEntryClick(object sender, EventArgs e)
        {
            SelectNewEntryRow();
        }

        private void SelectNewEntryRow()
        {
            int column = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex : 0;
            grid.CurrentCell = grid.Rows[grid.NewRowIndex].Cells[column];

            UpdateButtons();
        }

        private void OnRemoveEntriesClick(object sender, EventArgs e)
        {
            RemoveSelectedEntries();
        }

        private void RemoveSelectedEntries()
        {
            if (GetSelectionSize() == 0)
            {
                return;
            }

            List<EditedEntryDescriptor> deleteCompletely = new List<EditedEntryDescriptor>();
            EditedEntryDescriptor selection = null;

            int column = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex : 0;
            int row = LastSelectedRowIndex;

            if (++row < _entries.Count)
            {
                selection = _entries[row];
            }

            foreach (EditedEntryDescriptor descriptor in GetSelection())
            {
                if (descriptor.State == EditedItemState.Normal)
                {
                    // must be set to update entries correctly afterwards
                    descriptor.State = EditedItemState.Deleted;

                    _deletedEntries.Add(descriptor);
                }

                deleteCompletely.Add(descriptor);
            }

            AlterEntries(delegate
                             {
                                 foreach (EditedEntryDescriptor descriptor in deleteCompletely)
                                 {
                                     entriesBindingSource.Remove(descriptor);
                                 }

                                 return null;
                             });

            if (selection == null)
            {
                selection = _entries.LastOrDefault();
            }

            if (selection != null)
            {
                Select(selection, column);
            }

            UpdateButtons();
        }

        private void OnFormClosing(object sender, FormClosingEventArgs e)
        {
            if (IsModified())
            {
                if (DialogResult == DialogResult.Cancel)
                {
                    // this applies also for new dictionary without any content

                    DialogResult result = MessageBox.Show(Resources.SaveChangedDictionary, Resources.MessageBoxCaption, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                    if (result != DialogResult.Yes)
                    {
                        cbDictionaries.SelectedItem = _currentDictionary.Dictionary;
                        if (result == DialogResult.Cancel)
                        {
                            e.Cancel = true;
                        }
                        return;
                    }
                }

                if (!SaveChanges())
                {
                    e.Cancel = true;
                }
            }
        }

        private bool SaveChanges()
        {
            string path = tbFileName.Text.Trim();

            try
            {
                _savingChanges = true;

                if (!Utilities.IsNotEmpty(tbFileName.Text, cbFrom.Code, cbTo.Code, tbName.Text))
                {
                    MessageBox.Show(Resources.IncompleteDictionaryMetadata, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Stop);

                    return false;
                }

                bool creatingNewDictionary = _currentDictionary.Dictionary == null;

                if (creatingNewDictionary)
                {
                    if (File.Exists(path))
                    {
                        DialogResult answer = MessageBox.Show(String.Format(Resources.FileWillBeOverwriten, path), Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                        switch (answer)
                        {
                            case DialogResult.No:
                                return false;
                            case DialogResult.Yes:
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }

                    foreach (Dictionary dict in _dicts)
                    {
                        if (Utilities.Equals(dict.UserData.Path, path))
                        {
                            MessageBox.Show(String.Format(Resources.DictionaryAlreadyUsed, path), Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Stop);

                            return false;
                        }
                    }

                    try
                    {
                        using (File.Create(path))
                        {
                        }
                    }
                    catch (Exception e)
                    {
                        Logging.Error(GetType(), e, "File '{0}' cannot be created.", path);

                        MessageBox.Show(Resources.DictionaryCreateError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Stop);

                        return false;
                    }

                    if (_currentDictionary == null)
                    {
                        throw new InvalidOperationException();
                    }

                    try
                    {
                        _currentDictionary.Dictionary = _root.CreateNewDictionary(path, tbName.Text, tbDescription.Text, cbFrom.Code, cbTo.Code);
                    }
                    catch (DictionaryException exception)
                    {
                        Logging.Error(GetType(), exception, "Dictionary could not be created.");

                        MessageBox.Show(Resources.DictionaryCreateError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

                        return false;
                    }

                    _dicts.Add(_currentDictionary.Dictionary);
                    CreatedDictionary = _currentDictionary.Dictionary;
                }
                else
                {
                    _currentDictionary.Dictionary.UpdateMetadata(tbName.Text, tbDescription.Text, cbFrom.Code, cbTo.Code);
                }

                List<EditedEntryDescriptor> editedEntries = new List<EditedEntryDescriptor>();

                // modified entries
                foreach (EditedEntryDescriptor descriptor in _entries)
                {
                    if (descriptor.IsModified() || descriptor.State == EditedItemState.New)
                    {
                        editedEntries.Add(descriptor);
                    }
                }

                // removed entries
                foreach (EditedEntryDescriptor descriptor in _deletedEntries)
                {
                    editedEntries.Add(descriptor);
                }

                _currentDictionary.Dictionary.UpdateEntries(editedEntries);

                // name may have changed or new dictionary may have been created and we have to add it into list

                FillDictionaries();

                return true;
            }
            catch (DictionaryException exception)
            {
                Logging.Error(GetType(), exception, "Dictionary could not be saved.");

                MessageBox.Show(Resources.DictionarySaveError, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Error);

                return false;
            }
            finally
            {
                _savingChanges = false;
            }
        }

        private bool IsModified()
        {
            // new dictionary is always modified

            if (_currentDictionary.Dictionary == null)
            {
                return true;
            }

            // entries

            if (_deletedEntries.Count > 0)
            {
                return true;
            }

            foreach (EditedEntryDescriptor entryWrapper in _entries)
            {
                if (entryWrapper.IsModified() || entryWrapper.State != EditedItemState.Normal)
                {
                    return true;
                }
            }

            // metadata

            return tbFileName.Modified || tbName.Modified || cbFrom.Modified || cbTo.Modified || tbDescription.Modified;
        }

        private void OnSelectedDictionaryChanged(object sender, EventArgs e)
        {
            if (_skipChangedDictionaryCounter > 0 || _currentDictionary.Dictionary == cbDictionaries.SelectedItem)
            {
                return;
            }

            if (IsModified())
            {
                try
                {
                    _skipChangedDictionaryCounter++;

                    DialogResult result = MessageBox.Show(Resources.SaveChangedDictionary, Resources.MessageBoxCaption, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);

                    if (result == DialogResult.Cancel)
                    {
                        cbDictionaries.SelectedItem = _currentDictionary.Dictionary;
                        return;
                    }
                    else if (result == DialogResult.Yes)
                    {
                        if (!SaveChanges())
                        {
                            cbDictionaries.SelectedItem = _currentDictionary.Dictionary;
                            return;
                        }
                    }
                }
                finally
                {
                    _skipChangedDictionaryCounter--;
                }
            }

            _currentDictionary.Dictionary = cbDictionaries.SelectedItem as Dictionary;

            FillEntries();
            FillMetadata();
            UpdateButtons();
        }

        private void OnSaveClick(object sender, EventArgs e)
        {
            if (SaveChanges())
            {
                FillEntries();
                FillMetadata();
                UpdateButtons();
            }
        }

        private void UpdateMainToolbarButtons()
        {
            bool modified = IsModified();

            tsbSave.Enabled = modified;
            tsbRevert.Enabled = modified && _currentDictionary != null && _currentDictionary.Dictionary != null;
        }

        private void UdpateEntriesToolbar()
        {
            int selectionSize = GetSelectionSize();
            tsbRemoveEntries.Enabled = selectionSize > 0;
            tsbCopy.Enabled = selectionSize > 0;
            tsbCut.Enabled = selectionSize > 0;
            tsbPaste.Enabled = !IsNewRowSelected();
            tsbDisplayEntry.Enabled = selectionSize > 0;
            tsbExcludeFromImport.Enabled = selectionSize > 0;
            tsbEditEntry.Enabled = selectionSize > 0;
            tsbSelectAll.Enabled = _entries.Count > 0;
            tsbPriority.Enabled = selectionSize > 0;
            tsbAddEntry.Enabled = !(selectionSize == 0 && (grid.CurrentCell == null || grid.CurrentCell.RowIndex == grid.NewRowIndex));
        }

        private bool CanSelectedEntriesBeDisplayed()
        {
            if (_currentDictionary.Dictionary == null)
            {
                return false;
            }

            foreach (EditedEntryDescriptor descriptor in GetSelection())
            {
                if (descriptor.State != EditedItemState.Normal || descriptor.IsModified())
                {
                    return false;
                }
            }

            return true;
        }

        private void OnEntriesContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            int selectionSize = GetSelectionSize();

            miRemoveEntries.Enabled = selectionSize > 0;
            miCopyEntries.Enabled = selectionSize > 0;
            miCutEntries.Enabled = selectionSize > 0;
            miPasteEntries.Enabled = !IsNewRowSelected();
            miDisplayEntries.Enabled = selectionSize > 0;
            miExcludeFromImport.Enabled = selectionSize > 0;
            miTranslate.Enabled = selectionSize > 0;
            miPriority.Enabled = selectionSize > 0;
            miEditExample.Enabled = selectionSize > 0;
        }

        private void OnRevertClick(object sender, EventArgs e)
        {
            Revert();
        }

        private void Revert()
        {
            FillEntries();
            FillMetadata();
            UpdateButtons();
        }

        private void UpdateButtons()
        {
            UpdateMainToolbarButtons();
            UdpateEntriesToolbar();
        }

        private void OnMetadataChanged(object sender, EventArgs e)
        {
            UpdateButtons();
        }

        private void OnDictionaryPathBrowse(object sender, EventArgs e)
        {
            if (Directory.Exists(_root.MachineSettings.LastBrowseDirectory))
            {
                sfdDictionary.InitialDirectory = _root.MachineSettings.LastBrowseDirectory;
            }

            if (sfdDictionary.ShowDialog() == DialogResult.OK)
            {
                tbFileName.Text = sfdDictionary.FileName;
                string directory = Path.GetDirectoryName(sfdDictionary.FileName);
                if (!String.IsNullOrEmpty(directory) && Directory.Exists(directory))
                {
                    using (_root.MachineSettings.Edit())
                    {
                        _root.MachineSettings.LastBrowseDirectory = Path.GetDirectoryName(sfdDictionary.FileName);
                    }
                }
            }
        }

        private void OnFormClosed(object sender, FormClosedEventArgs e)
        {
            _currentDictionary.Dictionary = null;

            _root.MachineSettings.CheckIn();
        }

        private void OnDisplayEntriesClick(object sender, EventArgs e)
        {
            if (!CanSelectedEntriesBeDisplayed())
            {
                MessageBox.Show(Resources.EntriesMustBeSavedBeforeDisplaying, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            else
            {
                foreach (EditedEntryDescriptor descriptor in GetSortedSelection())
                {
                    _root.DisplayEntry(_currentDictionary.Dictionary, descriptor.Id);
                }
            }
        }

        private ClipboardEntries CreateDataObjectFromSelectedEntries()
        {
            ClipboardEntries dataObject = new ClipboardEntries();

            if (_currentDictionary.Dictionary != null)
            {
                dataObject.From = _currentDictionary.Dictionary.Entries.From;
                dataObject.To = _currentDictionary.Dictionary.Entries.To;
            }

            dataObject.Items = new List<ClipboardEntries.ClipboardEntry>();

            foreach (EditedEntryDescriptor descriptor in GetSortedSelection())
            {
                ClipboardEntries.ClipboardEntry clipboardEntry = new ClipboardEntries.ClipboardEntry();

                clipboardEntry.From = descriptor.From;
                clipboardEntry.To = descriptor.To;
                clipboardEntry.Example = descriptor.Example;
                clipboardEntry.Priority = descriptor.Priority;

                dataObject.Items.Add(clipboardEntry);
            }

            return dataObject;
        }

        private void OnCopyEntriesClick(object sender, EventArgs e)
        {
            DoCopy();
        }

        private void DoCopy()
        {
            CopySelectedEntries();
        }

        private void CopySelectedEntries()
        {
            ClipboardEntries clipboardEntries = CreateDataObjectFromSelectedEntries();
            clipboardEntries.Copy();
        }

        private void OnPasteEntriesClick(object sender, EventArgs e)
        {
            DoPaste();
        }

        private void DoPaste()
        {
            EditedEntryDescriptor descriptor = null;

            if (Clipboard.ContainsData(ClipboardEntries.CustomFormatName))
            {
                ClipboardEntries clipboardEntries = (ClipboardEntries)Clipboard.GetData(ClipboardEntries.CustomFormatName);

                if (clipboardEntries != null && !clipboardEntries.IsEmpty)
                {
                    bool swap = false;
                    string currentFrom = cbFrom.Code;
                    string currentTo = cbTo.Code;

                    if (!Utilities.Equals(currentFrom, clipboardEntries.From) && (Utilities.Equals(currentFrom, clipboardEntries.To) || Utilities.Equals(currentTo, clipboardEntries.From)))
                    {
                        swap = true;
                    }

                    AlterEntries(delegate
                    {
                        foreach (ClipboardEntries.ClipboardEntry clipboardEntry in clipboardEntries.Items)
                        {
                            descriptor = EditedEntryDescriptor.CreateNew();
                            descriptor.From = swap ? clipboardEntry.To : clipboardEntry.From;
                            descriptor.To = swap ? clipboardEntry.From : clipboardEntry.To;
                            descriptor.Example = clipboardEntry.Example;
                            descriptor.Priority = clipboardEntry.Priority;

                            entriesBindingSource.Add(descriptor);
                        }

                        return descriptor;
                    });
                }
            }
            else if (Clipboard.ContainsData(DataFormats.Text))
            {
                string text = (string)Clipboard.GetData(DataFormats.Text);

                if (!String.IsNullOrEmpty(text))
                {
                    string[] entries = text.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

                    AlterEntries(delegate
                    {
                        foreach (string entry in entries)
                        {
                            string[] parts = entry.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                            if (parts.Length > 0)
                            {
                                descriptor = EditedEntryDescriptor.CreateNew();
                                descriptor.From = parts[0].Trim();
                                if (parts.Length > 1)
                                {
                                    descriptor.To = parts[1].Trim();

                                    if (parts.Length > 2)
                                    {
                                        descriptor.Example = parts[2].Trim();
                                    }
                                }

                                entriesBindingSource.Add(descriptor);
                            }
                        }

                        return descriptor;
                    });
                }
            }

            UpdateButtons();
        }

        private void OnCutEntriesClick(object sender, EventArgs e)
        {
            DoCut();
        }

        private void DoCut()
        {
            CopySelectedEntries();
            RemoveSelectedEntries();
        }

        private void OnExcludeFromImport(object sender, EventArgs e)
        {
            ExcludeSelectedFromImport();
        }

        private void ExcludeSelectedFromImport()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(_root.ImportExcludes.Words.Value != null ? _root.ImportExcludes.Words.Value.TrimEnd(new[] { ' ', ';' }) : String.Empty);

            if (sb.Length > 0)
            {
                sb.Append("; ");
            }

            foreach (EditedEntryDescriptor descriptor in GetSortedSelection())
            {
                sb.Append(descriptor.From);
                sb.Append("; ");
            }

            sb.Remove(sb.Length - 2, 2);

            _root.ImportExcludes.Words.Value = sb.ToString();
        }

        private void OnSelectAllClick(object sender, EventArgs e)
        {
            SelectAll();
        }

        private void OnEditSelected(object sender, EventArgs e)
        {
            CancelEditing();

            List<EditedEntryDescriptor> descriptors = new List<EditedEntryDescriptor>();

            foreach (EditedEntryDescriptor descriptor in GetSortedSelection())
            {
                descriptors.Add(descriptor);
            }

            AdvancedEntryEditorForm form = new AdvancedEntryEditorForm(_root, descriptors, _currentDictionary.Dictionary);

            _root.FormsManager.Add(form);
            form.SkipSavingOnExit = true;
            form.PseudoParent = this;
            form.Show();

            form.Closed += delegate
                               {
                                   if (form.NewEntriesAvailable)
                                   {
                                       foreach (EditedEntryDescriptor descriptor in form.GetNewEntries())
                                       {
                                           entriesBindingSource.Add(descriptor);
                                       }
                                   }

                                   RefreshGrid();
                                   UpdateButtons();

                                   _root.FormsManager.Remove(form);
                               };
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Utilities.Is(keyData, Keys.Control | Keys.A) && grid.Focused)
            {
                SelectAll();
            }

            if (Utilities.Is(keyData, Keys.Insert) && !IsEditing())
            {
                SelectNewEntryRow();
            }

            if (Utilities.Is(keyData, Keys.F3) && !IsEditing() && tsbFind.Enabled)
            {
                FindText();
            }

            if (Utilities.Is(keyData, Keys.Delete) && !IsEditing())
            {
                RemoveSelectedEntries();
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        private void OnEditDirectly(object sender, EventArgs e)
        {
            _root.UserSettings.EditEntriesDirectly.Revert();

            grid.EditMode = _root.UserSettings.EditEntriesDirectly.Value ? DataGridViewEditMode.EditOnEnter : DataGridViewEditMode.EditOnKeystrokeOrF2;
        }

        private void InitializeEditDirectlyButton()
        {
            tsbEditDirectly.Checked = _root.UserSettings.EditEntriesDirectly.Value;

            grid.EditMode = _root.UserSettings.EditEntriesDirectly.Value ? DataGridViewEditMode.EditOnEnter : DataGridViewEditMode.EditOnKeystrokeOrF2;
        }

        private void OnEditExample(object sender, EventArgs e)
        {
            EditedEntryDescriptor first = _entries[FirstSelectedRowIndex];

            if (first != null)
            {
                MemoForm editor = new MemoForm();

                _root.FormsManager.Add(editor);
                editor.SetText(first.Example);
                editor.SetMode(MemoForm.Mode.OkCancel);
                editor.ReadOnly = false;
                editor.PseudoParent = this;
                editor.Text = Resources.EditorWindowCaption;
                editor.FormClosed += delegate
                                         {
                                             if (editor.ResultAction == DialogResult.OK)
                                             {
                                                 foreach (EditedEntryDescriptor descriptor in GetSelection())
                                                 {
                                                     descriptor.Example = editor.GetText();
                                                 }

                                                 RefreshGrid();
                                             }

                                             UpdateButtons();

                                             _root.FormsManager.Remove(editor);
                                         };

                editor.Show();
            }
        }

        private void OnImportCsvClick(object sender, EventArgs e)
        {
            if (IsModified())
            {
                MessageBox.Show(Resources.MustSaveDict, Resources.MessageBoxCaption, MessageBoxButtons.OK,
                                MessageBoxIcon.Exclamation);
                return;
            }

            ImportContext context = new ImportContext();

            context.ImportWordDelegate = CanImportWord;
            context.MinWordLen = _root.UserSettings.MinImportedWordLen.Value;
            context.ExcludedWords = new List<string>(_root.ImportExcludes.GetParsedWords());
            context.UseRegex = _root.UserSettings.UseRegExForExcludedWords.Value;

            CsvImportForm editor = new CsvImportForm(_root);

            _root.FormsManager.Add(editor);
            editor.PseudoParent = this;
            editor.ImportContext = context;
            editor.FormClosed += delegate
                                     {
                                         if (editor.DialogResult == DialogResult.OK)
                                         {
                                             AlterEntries(delegate
                                                              {
                                                                  EditedEntryDescriptor descriptor = null;

                                                                  foreach (SimpleEntry entry in editor.Result)
                                                                  {
                                                                      descriptor = EditedEntryDescriptor.CreateNew();
                                                                      descriptor.From = entry.From;
                                                                      descriptor.To = entry.To;
                                                                      descriptor.Example = entry.Example;

                                                                      entriesBindingSource.Add(descriptor);
                                                                  }

                                                                  return descriptor;
                                                              });
                                         }

                                         UpdateButtons();

                                         _root.FormsManager.Remove(editor);
                                     };

            editor.Show();
        }

        private void OnFindClick(object sender, EventArgs e)
        {
            FindText();
        }

        private void FindText()
        {
            if (!tsbFind.Enabled)
            {
                return;
            }

            int startCol = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex + 1 : 0;
            int startRow = grid.CurrentCell != null ? grid.CurrentCell.RowIndex : 0;
            string findText = txFindText.Text.ToLower();

            while (true)
            {
                FindColumnType columnType = ComboBoxItem<FindColumnType>.GetSelection(tscbFindWhere);
                EditedEntryDescriptor item = null;
                int column = -1;

                foreach (EditedEntryDescriptor descriptor in _entries.Skip(startRow))
                {
                    if (FindWithinColumn(findText, descriptor, columnType, startCol, out column))
                    {
                        item = descriptor;

                        break;
                    }

                    startCol = 0;
                }

                if (item != null)
                {
                    Select(item, column);

                    break;
                }
                if (startRow <= 1)
                {
                    MessageBox.Show(Resources.FindDoesNotExist, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Information);

                    break;
                }
                if (MessageBox.Show(Resources.FindReachedEndMsg, Resources.MessageBoxCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    startRow = 0;
                    startCol = -1;

                    continue;
                }
                break;
            }
        }

        private static bool FindWithinColumn(string text, EditedEntryDescriptor descriptor, FindColumnType whereToFind, int startCol, out int foundInColumn)
        {
            foundInColumn = -1;

            if ((whereToFind == FindColumnType.All || whereToFind == FindColumnType.From) && startCol == 0)
            {
                if (descriptor.From != null && descriptor.From.ToLower().Contains(text))
                {
                    foundInColumn = 0;

                    return true;
                }
            }

            if ((whereToFind == FindColumnType.All || whereToFind == FindColumnType.To) && startCol <= 1)
            {
                if (descriptor.To != null && descriptor.To.ToLower().Contains(text))
                {
                    foundInColumn = 1;

                    return true;
                }
            }

            if ((whereToFind == FindColumnType.All || whereToFind == FindColumnType.Example) && startCol <= 2)
            {
                if (descriptor.Example != null && descriptor.Example.ToLower().Contains(text))
                {
                    foundInColumn = 2;

                    return true;
                }
            }

            return false;
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            //
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            if (keyData == Keys.Escape && (grid.IsCurrentCellInEditMode))
            {
                grid.CancelEdit();
                return true;
            }
            if (keyData == Keys.Enter && (txFindText.Focused || tscbFindWhere.Focused))
            {
                FindText();
                return true;
            }
            if (grid.Focused && !IsEditing())
            {
                if (tsbCopy.Enabled && (Utilities.Is(keyData, Keys.Control | Keys.C) || Utilities.Is(keyData, Keys.Control | Keys.Insert)))
                {
                    // this does not work because datagridview has its own copy handling which only can be altered by subclassing
                    DoCopy();
                    return true;
                }
                else if (tsbPaste.Enabled && (Utilities.Is(keyData, Keys.Control | Keys.V) || Utilities.Is(keyData, Keys.Shift | Keys.Insert)))
                {
                    DoPaste();
                    return true;
                }
                else if (tsbCut.Enabled && Utilities.Is(keyData, Keys.Control | Keys.X))
                {
                    DoCut();
                    return true;
                }
            }
            return base.ProcessDialogKey(keyData);
        }

        private void OnPreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
            //
        }

        private void OnFindTextChanged(object sender, EventArgs e)
        {
            tsbFind.Enabled = txFindText.Text.Length > 0;
        }

        private void OnGridSelectionChanged(object sender, EventArgs e)
        {
            UpdateButtons();
        }

        private void OnCellEdited(object sender, DataGridViewCellEventArgs e)
        {
            UpdateMainToolbarButtons();
        }

        #region Import

        private void OnImportClick(object sender, EventArgs e)
        {
            if (IsModified())
            {
                MessageBox.Show(Resources.MustSaveDict, Resources.MessageBoxCaption, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

                return;
            }

            //

            if (Directory.Exists(_root.MachineSettings.LastBrowseDirectory))
            {
                ofdImportSubtitles.InitialDirectory = _root.MachineSettings.LastBrowseDirectory;
            }

            // select file to import

            if (ofdImportSubtitles.ShowDialog() == DialogResult.OK)
            {
                using (_root.MachineSettings.Edit())
                {
                    _root.MachineSettings.LastBrowseDirectory = Path.GetDirectoryName(ofdImportSubtitles.FileName);
                }

                SrtImportContext context = new SrtImportContext();

                context.ImportWordDelegate = CanImportWord;
                context.CodeEncoding = _root.UserSettings.ImportEncoding.Value;
                context.MinWordLen = _root.UserSettings.MinImportedWordLen.Value;
                context.ExcludedWords = new List<string>(_root.ImportExcludes.GetParsedWords());
                context.UseRegex = _root.UserSettings.UseRegExForExcludedWords.Value;
                context.Files = ofdImportSubtitles.FileNames;

                ImportSubtitles(context);
            }
        }

        private static bool CanImportWord(ImportContext context, string word)
        {
            if (String.IsNullOrEmpty(word))
            {
                return false;
            }

            if (word.Length < context.MinWordLen)
            {
                return false;
            }

            // search in excluded words

            foreach (string excludedWord in context.ExcludedWords)
            {
                if (context.UseRegex)
                {
                    if (Regex.IsMatch(word, excludedWord, RegexOptions.IgnoreCase))
                    {
                        return false;
                    }
                }
                else
                {
                    if (String.Compare(word, excludedWord, true) == 0)
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private void ImportSubtitles(SrtImportContext context)
        {
            ImportThreadWorkerData worker = new ImportThreadWorkerData { Context = context };

            BusyForm form = new BusyForm(worker);
            _root.FormsManager.Add(form);

            form.PseudoParent = this;
            form.WaitCaption = Resources.ImportingSubtitles;
            form.Show();

            form.Closed += delegate
                               {
                                   if (!worker.Canceled)
                                   {
                                       AlterEntries(delegate
                                                        {
                                                            EditedEntryDescriptor descriptor = null;

                                                            foreach (
                                                                KeyValuePair<string, Sentence> word in worker.Result)
                                                            {
                                                                descriptor = EditedEntryDescriptor.CreateNew();
                                                                descriptor.From = word.Key;
                                                                descriptor.Example = word.Value.Text;

                                                                entriesBindingSource.Add(descriptor);
                                                            }

                                                            return descriptor;
                                                        });

                                       UpdateButtons();
                                   }

                                   _root.FormsManager.Remove(form);
                               };

            Thread thread = new Thread(ImportThreadWorker) { CurrentUICulture = Thread.CurrentThread.CurrentUICulture };
            thread.Start(worker);
        }

        private void AlterEntries(PerformDelegate performDelegate)
        {
            int column = grid.CurrentCell != null ? grid.CurrentCell.ColumnIndex : 0;
            EditedEntryDescriptor nextSelection = null;
            try
            {
                grid.SuspendLayout();
                nextSelection = performDelegate();
            }
            finally
            {
                if (nextSelection != null)
                {
                    Select(nextSelection, column);
                }
                grid.ResumeLayout();
            }
        }

        private static void ImportThreadWorker(object obj)
        {
            ImportThreadWorkerData worker = obj as ImportThreadWorkerData;
            if (worker == null)
            {
                throw new InvalidOperationException();
            }

            SubtitlesImporter subtitlesImporter = new SubtitlesImporter(worker, worker.Context);
            subtitlesImporter.Perform();

            worker.Result = subtitlesImporter.Result;
            worker.Exit();
        }

        private class ImportThreadWorkerData : ThreadWorkerData
        {
            public SrtImportContext Context { get; set; }
            public Dictionary<string, Sentence> Result { get; set; }
        }

        #endregion
    }
}